package net.keebook.webapp.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import javax.ejb.EJB;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.keebook.webapp.dao.KeeBookDao;
import net.keebook.webapp.dao.LoginMemoryDao;
import net.keebook.webapp.dao.SettingDao;
import net.keebook.webapp.dao.UserDao;
import net.keebook.webapp.entity.LoginMemory;
import net.keebook.webapp.entity.User;
import net.keebook.webapp.model.SessionModel;

import org.apache.log4j.Logger;

public class SessionFilter implements Filter {
	private String loginUrl = "";
	private String selectKeyboxUrl = "";
	private String createKeyboxUrl = "";
	private final List<String> allowUrl = new ArrayList<String>();
	private final List<String> allowWithoutKeeBookUrl = new ArrayList<String>();
	private final List<String> adminUrl = new ArrayList<String>();
	
	@EJB
	private SettingDao settingDao;
	@EJB
	private KeeBookDao keeBookDao;
	@EJB
	private LoginMemoryDao loginMemoryDao;
	@EJB
	private UserDao userDao;
	
	@Override
	public void destroy() {
		
	}
	
	private void logResult(String url, SessionModel sessionModel, boolean allow) {
		Logger.getLogger(this.getClass()).trace("Filtering for url=" + url + ", decision="+allow+", " + sessionModel.toString());
	}
	
	private boolean isAllowWithoutKeeBookUrl(String contextPath, String url) {
		for (String path : getAllowWithoutKeeBookUrl()) {
			if (url.startsWith(contextPath + path)) {
				return true;
			}
		}
		return false;
	}
	
	private boolean isAdminUrl(String contextPath, String url) {
		for (String path : getAdminUrl()) {
			if (url.startsWith(contextPath + path)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest)req;
		
		String url = ((HttpServletRequest)request).getRequestURI();
		String contextPath = ((HttpServletRequest)request).getContextPath();
		HttpSession session = ((HttpServletRequest)request).getSession(true);
		SessionModel sessionModel = SessionModel.getInstance(session);
		Long userId = sessionModel.getUserId();
		Long keeBookId = sessionModel.getKeeBookId();
		User user = null;
		
		Logger.getLogger(this.getClass()).trace("Incoming request for url=" + url + ", contextPath="+contextPath+", " + sessionModel.toString());
		
		// Validate session model
		if (url.endsWith(".xhtml") && sessionModel.getLoggedIn()) {
			user = getUserDao().get(userId);
			if (user == null || !user.getEnabled()) {
				sessionModel.clear();
			}
			if (sessionModel.getKeeBookSelected() && !getKeeBookDao().canAccess(userId, keeBookId)) {
				sessionModel.setKeeBookId(null);
			}
		}
		
		// Check for Auto-Login ("Remember me") Auth Token
		if (!sessionModel.getAuthKeyChecked()) {
			sessionModel.setAuthKeyChecked(true);
			if (request.getCookies() != null) {
				for (Cookie cookie : request.getCookies()) {
					if ("authKey".equals(cookie.getName())) {
						LoginMemory memory = getLoginMemoryDao().get(cookie.getValue());
						if (memory != null && memory.getUser() != null && memory.getUser().getEnabled()) {
							sessionModel.setUserId(memory.getUser().getId());
							sessionModel.setAdmin(memory.getUser().getAdmin());
							sendRedirect((HttpServletResponse)response, "/index.xhtml");
						}
					}
				}
			}
		}
		
		boolean loggedIn = sessionModel.getLoggedIn();
		boolean keeBookSelected = sessionModel.getKeeBookSelected();
		boolean allow = false;
		boolean publicUrl = false;
		
		// Check if URL is public
		for (String path : getAllowUrl()) {
			if (url.startsWith(contextPath + path)) {
				publicUrl = true;
			}
		}
		
		if (loggedIn) {
			// Logged in, but eventually not admin: Can access everything except admin urls
			allow = true;
			for (String path : getAdminUrl()) {
				if (url.startsWith(contextPath + path)) {
					// Check if admin
					if (user == null) {
						user = getUserDao().get(userId);
					}
					if (user != null && user.getAdmin()) {
						allow = true;
					} else {
						allow = false;
					}
					break;
				}
			}
		} else {
			// Not logged in: Only can access public urls
			if (publicUrl) {
				allow = true;
			}
		}
		
		if (allow) {
			// If logged in and got Invitation ID in session: redirect to join page
			if (loggedIn && !"".equals(sessionModel.getInvitationId())) {
				String invitationId = sessionModel.getInvitationId();
				sessionModel.setInvitationId("");
				logResult(url, sessionModel, false);
				sendRedirect((HttpServletResponse)response, "/join.xhtml?id="+invitationId);
				return;
			}
			if (url.startsWith(contextPath + "/join.xhtml")) {
				logResult(url, sessionModel, true);
				chain.doFilter(request, response);
				return;
			}
			if (!publicUrl && loggedIn && sessionModel.getRequireProfileCompletion() && !url.startsWith(contextPath + "/completeprofile.xhtml")) {
				if (user == null) {
					user = getUserDao().get(userId);
				}
				logResult(url, sessionModel, false);
				sendRedirect((HttpServletResponse)response, "/completeprofile.xhtml");
				return;
			}
			if (!publicUrl && loggedIn && !keeBookSelected && !isAllowWithoutKeeBookUrl(contextPath, url) && !isAdminUrl(contextPath, url) && !url.startsWith(contextPath + getSelectKeyboxUrl()) && !url.startsWith(contextPath + getCreateKeyboxUrl())) {
				if (getKeeBookDao().getList(userId).isEmpty()) {
					logResult(url, sessionModel, false);
					sendRedirect((HttpServletResponse)response, getCreateKeyboxUrl());
				} else {
					logResult(url, sessionModel, false);
					sendRedirect((HttpServletResponse)response, getSelectKeyboxUrl());
				}
			} else {
				logResult(url, sessionModel, true);
				chain.doFilter(request, response);
			}
		} else {
			logResult(url, sessionModel, false);
			sendRedirect((HttpServletResponse)response, "/login.xhtml");
		}
	}
	
	private void sendRedirect(HttpServletResponse response, String url) {
		String root = getSettingDao().get(SettingId.PUBLIC_URL).getValue();
		if (root.endsWith("/")) {
			root.substring(0, root.length()-2);
		}
		response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
		response.setHeader("Location", root + url);
	}

	@Override
	public void init(FilterConfig config) throws ServletException {
		setLoginUrl(config.getInitParameter("login-url"));
		setSelectKeyboxUrl(config.getInitParameter("select-keybox-url"));
		setCreateKeyboxUrl(config.getInitParameter("create-keybox-url"));
		StringTokenizer tokens = new StringTokenizer(config.getInitParameter("allow-url"), ",");
		while (tokens.hasMoreTokens()) {
			allowUrl.add(tokens.nextToken());
		}
		tokens = new StringTokenizer(config.getInitParameter("allow-without-keebook"), ",");
		while (tokens.hasMoreTokens()) {
			getAllowWithoutKeeBookUrl().add(tokens.nextToken());
		}
		tokens = new StringTokenizer(config.getInitParameter("admin-url"), ",");
		while (tokens.hasMoreTokens()) {
			adminUrl.add(tokens.nextToken());
		}
	}

	public String getLoginUrl() {
		return loginUrl;
	}

	public void setLoginUrl(String loginUrl) {
		this.loginUrl = loginUrl;
	}

	public List<String> getAllowUrl() {
		return allowUrl;
	}

	public List<String> getAdminUrl() {
		return adminUrl;
	}

	public String getSelectKeyboxUrl() {
		return selectKeyboxUrl;
	}

	public void setSelectKeyboxUrl(String selectKeyboxUrl) {
		this.selectKeyboxUrl = selectKeyboxUrl;
	}

	public String getCreateKeyboxUrl() {
		return createKeyboxUrl;
	}

	public void setCreateKeyboxUrl(String createKeyboxUrl) {
		this.createKeyboxUrl = createKeyboxUrl;
	}

	public KeeBookDao getKeeBookDao() {
		return keeBookDao;
	}

	public void setKeeBookDao(KeeBookDao keeBookDao) {
		this.keeBookDao = keeBookDao;
	}
	public SettingDao getSettingDao() {
		return settingDao;
	}

	public void setSettingDao(SettingDao settingDao) {
		this.settingDao = settingDao;
	}

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public LoginMemoryDao getLoginMemoryDao() {
		return loginMemoryDao;
	}

	public void setLoginMemoryDao(LoginMemoryDao loginMemoryDao) {
		this.loginMemoryDao = loginMemoryDao;
	}

	public List<String> getAllowWithoutKeeBookUrl() {
		return allowWithoutKeeBookUrl;
	}
}
